using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.IO;
using System.Linq;
using System.Web.Profile;
using Log = Microsoft.Samples.ServiceHosting.HelloFabric.ApplicationEnvironment;

namespace BlogEngine.Core.Azure.BlogProvider
{
    public class AzureBlogProvider : Providers.BlogProvider // TODO: Many implementations should be done in the same transaction (eg: what if the Delete works but not the Insert?)
    {
        private FileStorage _fileStorage;
        private DataProvider _dataProvider;

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (String.IsNullOrEmpty(name))
            {
                name = "AzureBlogProvider";
            }

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Azure Blog Provider");
            }

            base.Initialize(name, config);

            if (config.Count > 0) // Throw an exception if unrecognized attributes remain
            {
                foreach (var key in config.AllKeys)
                    if (!string.IsNullOrEmpty(config[key]))
                        throw new ProviderException("Unrecognized attribute: " + config[key]);
            }

            _fileStorage = new FileStorage();
            _dataProvider = new DataProvider(true);
        }



        public override Stream LoadFileFromStorage(string path)
        {
            return _fileStorage.Load(path.Replace("\\", "/"));
        }

        public override bool FileExistsInStorage(string path)
        {
            return _fileStorage.Exists(path.Replace("\\", "/"));
        }

        public override void SaveFileToStorage(string path, Stream file)
        {
            _fileStorage.Save(path.Replace("\\", "/"), file);
        }



        #region Implementation of BlogProvider

        public override Post SelectPost(Guid id)
        {
            var postRow = _dataProvider.SelectRow<Tables.Post>(id);
            var comRows = _dataProvider.SelectAllRelatedRows<Tables.Comment>(postRow);
            var catRows = _dataProvider.SelectAllRelatedRows<Tables.PostCategory>(postRow);
            var notRows = _dataProvider.SelectAllRelatedRows<Tables.PostNotification>(postRow);
            var tagRows = _dataProvider.SelectAllRelatedRows<Tables.PostTag>(postRow);

            var post = _dataProvider.Convert<Post, Tables.Post>(postRow);
            post.Comments.AddRange(_dataProvider.ConvertAll<Comment, Tables.Comment>(comRows));
            post.Comments.ForEach(e => e.Parent = post);
            post.Categories.AddRange(catRows.Select(e => SelectCategory(e.CategoryId)));
            post.NotificationEmails.AddRange(notRows.Select(e => e.Email).ToArray());
            post.Tags.AddRange(tagRows.Select(e => e.Tag).ToArray());

            return post;
        }

        public override void InsertPost(Post entity)
        {
            _dataProvider.InsertEntity<Post, Tables.Post>(entity);
            UpdateRelatedRows(entity);
        }

        public override void UpdatePost(Post entity)
        {
            _dataProvider.UpdateEntity<Post, Tables.Post>(entity);
            UpdateRelatedRows(entity);
        }

        private void UpdateRelatedRows(Post entity)
        {
            DeleteRelatedRows(entity);

            entity.Comments.ForEach(e => e.ParentId = e.Parent.Id); // TODO: This should be done by BlogEngine

            var comRows = _dataProvider.ConvertAll<Tables.Comment, Comment>(entity.Comments);
            var catRows = entity.Categories.Select(e => new Tables.PostCategory { ParentId  = entity.Id, CategoryId = e.Id }).ToList();
            var notRows = new List<Tables.PostNotification>();
            foreach (var email in entity.NotificationEmails)
                notRows.Add(new Tables.PostNotification { ParentId = entity.Id, Email = email });
            var tagRows = entity.Tags.Select(e => new Tables.PostTag { ParentId = entity.Id, Tag = e }).ToList();

            _dataProvider.InsertAllRelatedRows(comRows);
            _dataProvider.InsertAllRelatedRows(catRows);
            _dataProvider.InsertAllRelatedRows(notRows);
            _dataProvider.InsertAllRelatedRows(tagRows);
        }

        public override void DeletePost(Post entity)
        {
            _dataProvider.DeleteEntity<Post, Tables.Post>(entity);
            DeleteRelatedRows(entity);
        }

        private void DeleteRelatedRows(Post entity)
        {
            var postRow = _dataProvider.Convert<Tables.Post, Post>(entity);
            _dataProvider.DeleteAllRelatedRows<Tables.Comment>(postRow);
            _dataProvider.DeleteAllRelatedRows<Tables.PostCategory>(postRow);
            _dataProvider.DeleteAllRelatedRows<Tables.PostNotification>(postRow);
            _dataProvider.DeleteAllRelatedRows<Tables.PostTag>(postRow);
        }

        public override List<Post> FillPosts()
        {
            var posts = _dataProvider.SelectAllEntities<Post, Tables.Post>();

            var comments= _dataProvider.SelectAllEntities<Comment, Tables.Comment>();
            var catRows = _dataProvider.SelectAllRows<Tables.PostCategory>();
            var notRows = _dataProvider.SelectAllRows<Tables.PostNotification>();
            var tagRows = _dataProvider.SelectAllRows<Tables.PostTag>();

            foreach (var post in posts)
            {
                foreach (var e in comments)
                    if (e.ParentId == post.Id)
                    {
                        e.Parent = post;
                        post.Comments.Add(e);
                    }
                foreach (var e in catRows)
                    if (e.ParentId == post.Id)
                        post.Categories.Add(SelectCategory(e.CategoryId));
                foreach (var e in notRows)
                    if (e.ParentId == post.Id)
                        post.NotificationEmails.Add(e.Email);
                foreach (var e in tagRows)
                    if (e.ParentId == post.Id)
                        post.Tags.Add(e.Tag);
            }
            return posts;
        }


        public override Page SelectPage(Guid id)
        {
            return _dataProvider.SelectEntity<Page, Tables.Page>(id);
        }

        public override void InsertPage(Page entity)
        {
            _dataProvider.InsertEntity<Page, Tables.Page>(entity);
        }

        public override void UpdatePage(Page entity)
        {
            _dataProvider.UpdateEntity<Page, Tables.Page>(entity);
        }

        public override void DeletePage(Page entity)
        {
            _dataProvider.DeleteEntity<Page, Tables.Page>(entity);
        }

        public override List<Page> FillPages()
        {
            return _dataProvider.SelectAllEntities<Page, Tables.Page>();
        }


        public override AuthorProfile SelectProfile(string id)
        {
            return (AuthorProfile)UserProfile.GetUserProfile(id);
        }

        public override void InsertProfile(AuthorProfile profile)
        {
            ((UserProfile)profile).Save();
        }

        public override void UpdateProfile(AuthorProfile profile)
        {
            ((UserProfile)profile).Save();
        }

        public override void DeleteProfile(AuthorProfile profile)
        {
            ProfileManager.DeleteProfile(profile.UserName);
        }

        public override List<AuthorProfile> FillProfiles()
        {
            var profiles = new List<AuthorProfile>();
            foreach (ProfileInfo pi in ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All))
                profiles.Add((AuthorProfile)UserProfile.GetUserProfile(pi.UserName));
            return profiles;
        }


        public override Category SelectCategory(Guid id) // TODO: With multiple WebRoles, we cannot rely on static Lists + But without them, make sure to not ask each item one by one (=> ask all, then look in it)
        {
            return Category.Categories.Single(e => e.Id == id);
        }

        public override void InsertCategory(Category entity)
        {
            _dataProvider.InsertEntity<Category, Tables.Category>(entity);
            Category.Categories.Add(entity);
        }

        public override void UpdateCategory(Category entity)
        {
            _dataProvider.UpdateEntity<Category, Tables.Category>(entity);
        }

        public override void DeleteCategory(Category entity)
        {
            _dataProvider.DeleteEntity<Category, Tables.Category>(entity);
            Category.Categories.Remove(entity);
        }

        public override List<Category> FillCategories()
        {
            return _dataProvider.SelectAllEntities<Category, Tables.Category>();
        }


        public override StringDictionary LoadSettings()
        {
            var rows = _dataProvider.SelectAllRows<Tables.Setting>();
            var values = new StringDictionary();
            foreach (var row in rows)
                values.Add(row.Name, row.Value);
            return values;
        }

        public override void SaveSettings(StringDictionary values)
        {
            _dataProvider.DeleteAllRows<Tables.Setting>();
            var rows = new List<Tables.Setting>(values.Count);
            foreach (System.Collections.DictionaryEntry v in values)
                rows.Add(new Tables.Setting { Name = v.Key.ToString(), Value = v.Value.ToString() });
            _dataProvider.InsertAllRows(rows);
        }

        public override StringCollection LoadPingServices()
        {
            var rows = _dataProvider.SelectAllRows<Tables.PingService>();
            var values = new StringCollection();
            values.AddRange(rows.Select(e => e.Url).ToArray());
            return values;
        }

        public override void SavePingServices(StringCollection values)
        {
            _dataProvider.DeleteAllRows<Tables.PingService>();
            var rows = new List<Tables.PingService>(values.Count);
            foreach (var v in values)
                rows.Add(new Tables.PingService { Url = v });
            _dataProvider.InsertAllRows(rows);
        }

        public override StringCollection LoadStopWords()
        {
            var rows = _dataProvider.SelectAllRows<Tables.StopWord>();
            var stopWords = new StringCollection();
            stopWords.AddRange(rows.Select(e => e.Value).ToArray());
            return stopWords;
        }


        public override object LoadFromDataStore(DataStore.ExtensionType exType, string exId)
        {
            var row = _dataProvider.SelectRow<Tables.DataStoreSetting>(exId);
            return row == null ? null : row.Setting;
        }

        public override void SaveToDataStore(DataStore.ExtensionType exType, string exId, object settingsObject)
        {
            var serializer = new System.Xml.Serialization.XmlSerializer(settingsObject.GetType());
            string setting;
            using (var stringWriter = new StringWriter())
            {
                serializer.Serialize(stringWriter, settingsObject);
                setting = stringWriter.ToString();
            }

            _dataProvider.InsertOrUpdateRow(
                new Tables.DataStoreSetting { ExtensionType = (int)exType, ExtensionId = exId, Setting = setting });
        }

        public override void RemoveFromDataStore(DataStore.ExtensionType exType, string exId)
        {
            _dataProvider.DeleteRow(
                new Tables.DataStoreSetting { ExtensionType = (int)exType, ExtensionId = exId });
        }


        public override BlogRollItem SelectBlogRollItem(Guid id)
        {
            return _dataProvider.SelectEntity<BlogRollItem, Tables.BlogRollItem>(id);
        }

        public override void InsertBlogRollItem(BlogRollItem entity)
        {
            _dataProvider.InsertEntity<BlogRollItem, Tables.BlogRollItem>(entity);
        }

        public override void UpdateBlogRollItem(BlogRollItem entity)
        {
            _dataProvider.UpdateEntity<BlogRollItem, Tables.BlogRollItem>(entity);
        }

        public override void DeleteBlogRollItem(BlogRollItem entity)
        {
            _dataProvider.DeleteEntity<BlogRollItem, Tables.BlogRollItem>(entity);
        }

        public override List<BlogRollItem> FillBlogRoll()
        {
            return _dataProvider.SelectAllEntities<BlogRollItem, Tables.BlogRollItem>();
        }


        public override Referrer SelectReferrer(Guid id)
        {
            return _dataProvider.SelectEntity<Referrer, Tables.Referrer>(id);
        }

        public override void InsertReferrer(Referrer entity)
        {
            _dataProvider.InsertEntity<Referrer, Tables.Referrer>(entity);
        }

        public override void UpdateReferrer(Referrer entity)
        {
            _dataProvider.UpdateEntity<Referrer, Tables.Referrer>(entity);
        }

        /* TODO: public override void DeleteReferrer(Referrer entity)
        {
            _dataProvider.DeleteEntity<Referrer, Tables.Referrer>(entity);
        }*/

        public override List<Referrer> FillReferrers()
        {
            return _dataProvider.SelectAllEntities<Referrer, Tables.Referrer>();
        }


        public override string StorageLocation()
        {
            return @"/";
        }

        #endregion
    }
}